using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.EventSystems;

namespace LS
{
    [System.Serializable]
    public struct RTSSelectionSetting
    {
        public float SelectMaxZDistanceFromCamera;
        public Unity.Physics.Authoring.PhysicsCategoryTags SelectFilter;
        public Unity.Physics.Authoring.PhysicsCategoryTags LeftClickFilter;
        public Unity.Physics.Authoring.PhysicsCategoryTags RightClickFilter;
    }

    public class RTSSelectionInputHolder : MonoBehaviour
    {
        public Camera InputCamera;

        [SerializeField]
        private RTSSelectionSetting m_SelectionSetting = new RTSSelectionSetting()
        {
            SelectMaxZDistanceFromCamera = 1000,
            SelectFilter = Unity.Physics.Authoring.PhysicsCategoryTags.Everything,
            RightClickFilter = Unity.Physics.Authoring.PhysicsCategoryTags.Everything,
            LeftClickFilter = Unity.Physics.Authoring.PhysicsCategoryTags.Everything,
        };

        private CameraSelectionPainter m_CameraPainter;
        private PlayerInputHolder m_PlayerInputHolder;
        private LeftSwipeState m_LeftSwipeState;
        private LeftClickState m_LeftClickState;
        private RightClickState m_RightClickState;

        private EntityQuery selectionComponentQuery;
        private World m_DefaultWorld;
        private void Awake()
        {
#if UNITY_6000_0_OR_NEWER
            m_CameraPainter = FindFirstObjectByType<CameraSelectionPainter>();
            m_PlayerInputHolder = FindFirstObjectByType<PlayerInputHolder>();
#else
            m_CameraPainter = FindObjectOfType<CameraSelectionPainter>();
            m_PlayerInputHolder = FindObjectOfType<PlayerInputHolder>();
#endif
            m_CameraPainter.enabled = false;

        }

        private void Start()
        {
            m_LeftSwipeState = m_PlayerInputHolder.GetInputState<LeftSwipeState>();
            m_LeftClickState = m_PlayerInputHolder.GetInputState<LeftClickState>();
            m_RightClickState = m_PlayerInputHolder.GetInputState<RightClickState>();
            m_DefaultWorld = World.DefaultGameObjectInjectionWorld;
            selectionComponentQuery = m_DefaultWorld.EntityManager.CreateEntityQuery(typeof(RTSSelectionSettingComponentData), typeof(RTSSelectionStateComponentData));
        }

        private void Update()
        {
            if (m_LeftSwipeState.Phase == InputPhase.Start)
            {
                PlayerSelection.SelectionOnUI = EventSystem.current.IsPointerOverGameObject();
            }
            else if (m_LeftSwipeState.Phase == InputPhase.Non)
            {
                PlayerSelection.SelectionOnUI = false;
            }
            //PlayerSelection.Selecting = !PlayerSelection.SelectionOnUI && m_LeftSwipeState.IsPressed && m_LeftSwipeState.Phase == InputPhase.Update;

            RTSSelectionSettingComponentData settingComponent = selectionComponentQuery.GetSingleton<RTSSelectionSettingComponentData>();
            RefRW<RTSSelectionStateComponentData> stateComponent = selectionComponentQuery.GetSingletonRW<RTSSelectionStateComponentData>();
            
            if (!settingComponent.Initialized)
            {
                InitializeSetting(ref settingComponent);
                settingComponent.Initialized = true;
                var entity = selectionComponentQuery.GetSingletonEntity();
                m_DefaultWorld.EntityManager.SetComponentData(entity, settingComponent);
            }

            UpdateCameraState(settingComponent, stateComponent);
            LeftSelection(settingComponent, stateComponent);
            LeftClick(settingComponent, stateComponent);
            RightClick(settingComponent, stateComponent);
        }

        private void InitializeSetting(ref RTSSelectionSettingComponentData setting)
        {
            setting.SelectMaxZDistance = this.m_SelectionSetting.SelectMaxZDistanceFromCamera;
            setting.SelectFilter = this.m_SelectionSetting.SelectFilter.Value;
            setting.LeftClickFilter = this.m_SelectionSetting.LeftClickFilter.Value;
            setting.RightClickFilter = this.m_SelectionSetting.RightClickFilter.Value;
        }

        private void UpdateToSelectionPainter(bool selecting,Vector2 start,Vector2 current)
        {
            m_CameraPainter.enabled = selecting;
            m_CameraPainter.StartScreenPoint = start;
            m_CameraPainter.EndScreenPoint = current;
        }

        private bool LeftClick(RTSSelectionSettingComponentData setting, RefRW<RTSSelectionStateComponentData> state)
        {
            if (PlayerSelection.SelectionOnUI || !m_LeftClickState.IsPressed)
            {
                state.ValueRW.LeftClickInput.IsPressed = false;
                return false;
            }
            Vector3 screenPoint = m_LeftClickState.PreviousPoint;
            screenPoint.z = InputCamera.nearClipPlane;
            var ray = this.InputCamera.ScreenPointToRay(screenPoint);
            state.ValueRW.LeftClickInput.IsPressed = true;
            state.ValueRW.LeftClickInput.RaycastInputFromCamera = new Unity.Physics.RaycastInput()
            {
                Start = ray.origin,
                End = ray.GetPoint(setting.SelectMaxZDistance),
                Filter = new Unity.Physics.CollisionFilter()
                {
                    CollidesWith = setting.LeftClickFilter,
                    BelongsTo = Unity.Physics.CollisionFilter.Default.BelongsTo,
                    GroupIndex = Unity.Physics.CollisionFilter.Default.GroupIndex
                }
            };
            return true;
        }

        private bool RightClick(RTSSelectionSettingComponentData setting, RefRW<RTSSelectionStateComponentData> state)
        {
            if (EventSystem.current.IsPointerOverGameObject(PointerInputModule.kMouseRightId) || !m_RightClickState.IsPressed)
            {
                state.ValueRW.RightClickInput.IsPressed = false;
                return false;
            }
            Vector3 screenPoint = m_RightClickState.PreviousPoint;
            screenPoint.z = InputCamera.nearClipPlane;
            var ray = this.InputCamera.ScreenPointToRay(screenPoint);
            state.ValueRW.RightClickInput.IsPressed = true;
            state.ValueRW.RightClickInput.RaycastInputFromCamera = new Unity.Physics.RaycastInput()
            {
                Start = ray.origin,
                End = ray.GetPoint(setting.SelectMaxZDistance),
                Filter = new Unity.Physics.CollisionFilter()
                {
                    CollidesWith = setting.RightClickFilter,
                    BelongsTo = Unity.Physics.CollisionFilter.Default.BelongsTo,
                    GroupIndex = Unity.Physics.CollisionFilter.Default.GroupIndex
                }
            };
            return true;
        }

        private bool LeftSelection(RTSSelectionSettingComponentData setting, RefRW<RTSSelectionStateComponentData> state)
        {
            InputPhase phase = m_LeftSwipeState.Phase;
            state.ValueRW.SelectionPhase = phase;
            if (PlayerSelection.SelectionOnUI || !m_LeftSwipeState.IsPressed)
            {
                PlayerSelection.Selecting = false;
                state.ValueRW.SelectionPressed = false;
                UpdateToSelectionPainter(false, m_LeftSwipeState.StartPoint, m_LeftSwipeState.PreviousPoint);
                return false;
            }
            PlayerSelection.Selecting = true;
            state.ValueRW.SelectionPressed = true;
            UpdateToSelectionPainter(PlayerSelection.Selecting,m_LeftSwipeState.StartPoint, m_LeftSwipeState.PreviousPoint);
            
            Vector3 cameraRight = InputCamera.transform.right;
            Vector3 cameraUp = InputCamera.transform.up;
            float cameraNear = InputCamera.nearClipPlane;
            float cameraFar = InputCamera.farClipPlane;

            Vector3 start = m_LeftSwipeState.StartPoint;
            start.z = InputCamera.farClipPlane;
            Vector3 end = m_LeftSwipeState.PreviousPoint;
            end.z = InputCamera.farClipPlane;

            Vector2 inputCenter = (end + start) / 2;
            Vector3 inputCenterOnCameraNear = InputCamera.ScreenToWorldPoint(new Vector3(inputCenter.x, inputCenter.y, cameraNear));

            Vector3 inputTop = new Vector3(inputCenter.x, Mathf.Max(end.y, start.y), cameraNear);
            Vector3 inputRight = new Vector3(Mathf.Max(end.x, start.x), inputCenter.y, cameraNear);
            Vector3 inputCenterHorizontalOffset = new Vector3(inputCenter.x, Screen.height / 2, cameraNear);
            Vector3 inputCenterVerticalOffset = new Vector3(Screen.width / 2, inputCenter.y, cameraNear);

            Vector3 inputNearCenter = inputCenterOnCameraNear;
            Vector3 inputNearRight = InputCamera.ScreenToWorldPoint(inputRight);
            Vector3 inputNearTop = InputCamera.ScreenToWorldPoint(inputTop);

            Vector3 centerHorizontalOffsetPosition = InputCamera.ScreenToWorldPoint(inputCenterHorizontalOffset);
            Vector3 centerVerticalOffsetPosition = InputCamera.ScreenToWorldPoint(inputCenterVerticalOffset);
            Vector3 screenToWorldCenter = InputCamera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, cameraNear));

            float inputHafWidth = Vector3.Distance(inputNearRight, inputNearCenter);
            float inputHafHeight = Vector3.Distance(inputNearTop, inputNearCenter);
            float centerOffsetX = Vector3.Distance(centerHorizontalOffsetPosition, screenToWorldCenter);
            float centerOffsetY = Vector3.Distance(centerVerticalOffsetPosition, screenToWorldCenter);

            int offsetXSign = Vector3.Dot(centerHorizontalOffsetPosition - screenToWorldCenter, cameraRight) > 0 ? 1 : -1;
            int offsetYSign = Vector3.Dot(centerVerticalOffsetPosition - screenToWorldCenter, cameraUp) > 0 ? 1 : -1;

            float left = centerOffsetX * offsetXSign - inputHafWidth;
            float right = centerOffsetX * offsetXSign + inputHafWidth;
            float top = centerOffsetY * offsetYSign + inputHafHeight;
            float bottom = centerOffsetY * offsetYSign - inputHafHeight;

            Matrix4x4 selectionProjection = Matrix4x4.Frustum(
                left: left,
                right: right,
                bottom: bottom,
                top: top,
                zNear: cameraNear,
                zFar: cameraFar
                );

            var planes = GeometryUtility.CalculateFrustumPlanes(selectionProjection * InputCamera.worldToCameraMatrix);
            for (int i = 0; i < planes.Length; i++)
            {
                state.ValueRW.FrustumBounds[i] = new Unity.Mathematics.Geometry.Plane(planes[i].normal, planes[i].distance);
            }

            return true;
        }

        private void UpdateCameraState(RTSSelectionSettingComponentData setting, RefRW<RTSSelectionStateComponentData> state)
        {
            Vector3 cameraPosition = this.InputCamera.transform.position;
            Vector3 cameraUp = this.InputCamera.transform.up;
            Vector3 cameraRight = this.InputCamera.transform.right;
            Vector3 cameraForward = this.InputCamera.transform.forward;
            Quaternion cameraRotation = this.InputCamera.transform.rotation;

            state.ValueRW.CameraTransform = new TransformState()
            {
                Forward = cameraForward,
                Right = cameraRight,
                Up = cameraUp,
                Position = cameraPosition,
                Rotation = cameraRotation,
                Scale = new float3(1)
            };
        }
    }
}
